﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Phone.Controls;
using Wp7Tools.Services;
using Wp7Tools.Services.Cache;
using Wp7Tools.Util;
using GestureEventArgs = Microsoft.Phone.Controls.GestureEventArgs;

namespace Wp7Tools.Controls {
	public partial class AsyncCachedImage : UserControl {

		private static Semaphore _semaphore;
		private static Semaphore Semaphore {
			get {
				if(_semaphore == null) {
					_semaphore = new Semaphore(_maxConcurrentDownloads);
				}
				return _semaphore;
			}
		}

		private static int _maxConcurrentDownloads = 6;
		public static void SetMaxConcurrentDownloads(int max) {
			if(_semaphore != null) {
				throw new InvalidOperationException("You can only set max concurrent downloads before instantiating any AsyncImage. Try it in App.xaml");
			}
			_maxConcurrentDownloads = max;
		}
		
		public static LocalCache Cache = new LocalCache(new DataStorage(), "ImageCache");
		public static int DelayToDispatcher { get; set; }
		private static Random _random = new Random(DateTime.Now.Millisecond);

		private Uri _imageUri;
		private bool _pivotSearched;
		private LockablePivot _pivot;

		public AsyncCachedImage() {
			InitializeComponent();
		}

		public Uri ImageSource {
			get { return (Uri)GetValue(ImageSourceProperty); }
			set { SetValue(ImageSourceProperty, value); }
		}

		public static readonly DependencyProperty ImageSourceProperty =
			DependencyProperty.Register("ImageSource",
										typeof(Uri), typeof(AsyncCachedImage), new PropertyMetadata(OnImageChange));

		private static void OnImageChange(DependencyObject d, DependencyPropertyChangedEventArgs e) {
			Uri uri = (Uri)e.NewValue;
			AsyncCachedImage asyncCachedImage = (AsyncCachedImage)d;
			asyncCachedImage._imageUri = uri;

			if (DesignerProperties.IsInDesignTool || !uri.IsAbsoluteUri) {
				asyncCachedImage.Image.Source = new BitmapImage(uri);
				return;
			}
			ThreadPool.QueueUserWorkItem(LimitGoGetImage, asyncCachedImage);
		}

		private static void LimitGoGetImage(object state) {
			Semaphore.Run(() => GoGetImage(state));
		}

		private static void GoGetImage(object state) {
			AsyncCachedImage asyncCachedImage = (AsyncCachedImage)state;

			byte[] imageInBytes = Cache.GetFromCache(UriHelper.ToFilename(asyncCachedImage._imageUri));
			if (imageInBytes != null) {
				Debug.WriteLine("Cached Image -> " + asyncCachedImage._imageUri);
				SetImageToVisual(asyncCachedImage, imageInBytes);
				return;
			}

			WebClient client = new WebClient();
			client.OpenReadCompleted += (s, args) => {
				if (args.Error != null) {
					Debug.WriteLine("AsyncImage -> Error downloading image:" + args.Error);
					return;
				}
				if (args.Cancelled) return;
				try {
					byte[] imageFromWeb = ReadFully(args.Result);
					Cache.SaveToCache(UriHelper.ToFilename(asyncCachedImage._imageUri), imageFromWeb);
					Debug.WriteLine("Cache Miss   -> " + asyncCachedImage._imageUri); 
					SetImageToVisual(asyncCachedImage, imageFromWeb);
				}
				catch (Exception ex) {
					Debug.WriteLine("AsyncImage -> Error converting stream to image:" + ex);
				}
			};
			client.OpenReadAsync(asyncCachedImage._imageUri, client);
		}


		private static void SetImageToVisual(AsyncCachedImage asyncCachedImage, byte[] imageInBytes) {
			MemoryStream memoryStream = new MemoryStream(imageInBytes);

			if(DelayToDispatcher > 0) {
				Thread.Sleep(_random.Next(0,DelayToDispatcher));	
			}
			Deployment.Current.Dispatcher.BeginInvoke(() => {
			    try {
			        BitmapImage im = new BitmapImage();
			        im.SetSource(memoryStream);
			        asyncCachedImage.Image.Source = im;	
			    }
			    catch {
			        Debug.WriteLine("Error rendering image: " + asyncCachedImage._imageUri);
			        //TODO: add broken image
			    }
			});
		}

		public static byte[] ReadFully(Stream input) {
			byte[] buffer = new byte[16 * 1024];
			using (MemoryStream ms = new MemoryStream()) {
				int read;
				while ((read = input.Read(buffer, 0, buffer.Length)) > 0) {
					ms.Write(buffer, 0, read);
				}
				return ms.ToArray();
			}
		}

		public string Text {
			get { return (string)GetValue(TextProperty); }
			set { SetValue(TextProperty, value); }
		}

		public static readonly DependencyProperty TextProperty =
			DependencyProperty.Register("Text", typeof(string), typeof(AsyncCachedImage), new PropertyMetadata(OnTextChanged));

		private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
			var control = ((AsyncCachedImage)d);
			control.Title.Text = (string)e.NewValue;
			control.TextBlockBox.Visibility = String.IsNullOrEmpty(control.Title.Text) ? Visibility.Collapsed : Visibility.Visible;
		}


		public bool EnableZoom {
			get { return (bool)GetValue(EnableZoomProperty); }
			set { SetValue(EnableZoomProperty, value); }
		}

		public static readonly DependencyProperty EnableZoomProperty =
			DependencyProperty.Register("EnableZoom", typeof(bool), typeof(AsyncCachedImage), null);

		private double _initialScale;

		private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e) {
			if (!EnableZoom) return;
			_initialScale = transform.ScaleX;
			Debug.WriteLine("OnPinchStarted");
		}

		private void OnPinchDelta(object sender, PinchGestureEventArgs e) {
			if (!EnableZoom) return;
			double scale = _initialScale * e.DistanceRatio;
			transform.ScaleX = scale;
			transform.ScaleY = scale;
			Debug.WriteLine("OnPinchDelta-> Scale: " + scale);
		}

		private void ImageDoubleTap(object sender, GestureEventArgs e) {
			if (!EnableZoom) return;
			ResetImage();
		}

		private void ResetImage() {
			transform.ScaleX = 1;
			transform.ScaleY = 1;
			transform.TranslateX = 0;
			transform.TranslateY = 0;
			transform.CenterX = 0.5;
			transform.CenterY = 0.5;
		}

		private void DragStarted(object sender, DragStartedGestureEventArgs e) {
			if (!EnableZoom) return;
			Debug.WriteLine("DragStarted");
		}

		private void DragDelta(object sender, DragDeltaGestureEventArgs e) {
			if (!EnableZoom || ScaleNotChanged()) return;
			transform.CenterX = transform.CenterX - e.HorizontalChange;
			transform.CenterY = transform.CenterY - e.VerticalChange;
			Debug.WriteLine("DragDelta-> H:{0} V:{1} ", e.HorizontalChange, e.VerticalChange);
		}

		private void DragCompleted(object sender, DragCompletedGestureEventArgs e) {
			if (!EnableZoom || ScaleNotChanged()) return;
			Debug.WriteLine("DragCompleted");
		}

		private void ImageManipulationStarted(object sender, ManipulationStartedEventArgs e) {
			if (!EnableZoom || ScaleNotChanged()) return;
			Image.SetValue(Canvas.ZIndexProperty, 1000);
			ChangeParentLockablePivotStateTo(true);
		}

		private void ImageManipulationCompleted(object sender, ManipulationCompletedEventArgs e) {
			if (!EnableZoom) return;
			ChangeParentLockablePivotStateTo(false);
		}

		private void ChangeParentLockablePivotStateTo(bool state) {
			if (!_pivotSearched) {
				_pivot = FindLockablePivot();
				_pivot.SelectionChanged += (s, a) => ResetImage();
				_pivotSearched = true;
			}
			if (_pivot != null) {
				_pivot.IsLocked = state;
			}
		}

		private LockablePivot FindLockablePivot() {
			DependencyObject parent = Image.Parent;
			while (parent != null) {
				if (parent is LockablePivot) {
					return parent as LockablePivot;
				}
				parent = VisualTreeHelper.GetParent(parent);
			}
			return null;
		}

		private bool ScaleNotChanged() {
			return Math.Abs((int) (transform.ScaleX - 1)) <= 0.1;
		}
	}
}